ブログで趣味でプログラミングからお料理まで呟いています。よろしくー。(^-^)/


トップページ > Perlについて

●Perlについて●

2023-02-06 20:08:40

良いコードについて:2023-02-2



さて、プログラミングでの良いコードについての記事を見つけました。
それはPythonで書かれたコードなので、Perlに書き直して、記事と合わせて載せたいと思います。
良いコードについて:2023-02-1の続きになります。

このサブルーチンを詳しく見ると実は「税込み合計」を算出する以外の責務を持っていることがわかります。
それが「商品の品種と価格の紐付け」です。

こういうデータを紐付ける責務は、具体的なデータを知っている側(つまり、このサブルーチンの利用者)が担当するべきです。
複数のデータを紐付ける、あるいは1つのデータにする方法は色々ありますが、今回は抽象データ型が利用できそうです。
PythonやJavaScriptではクラスの機能になります。
※Perlもクラスの機能を持たせることができます。

# total_price.pm package total_price; use strict; sub new { my $class = shift; my $self = { my $price => my $total_price }; return bless $self,$class; } sub item { my @item_list = @_; my %items; my $i = 0; my $monoflg = 0; foreach(@item_list){ if($_ =~ /food/){ # food $monoflg = 1; }elsif($monoflg == 1){ $items{"food_$i"} = $_; # 100yen $monoflg = 0; }elsif($_ =~ /item/){ #item $monoflg = 2; }elsif($monoflg == 2){ $items{"item_$i"} = $_; # 200yen $monoflg = 0; } $i++; } return \%items; } sub total_price { my $hashref = shift; my %shina = %{$hashref}; my $price = 0; my $total_price = 0; foreach(sort keys %shina){ if($_ =~ /food/ ){ $price = $shina{$_} * 1.08; # foodは8%消費税 }elsif($_ =~ /item/ ){ $price = $shina{$_} * 1.1; # itemは10%消費税 } $total_price = $total_price + $price; $price = 0; } return $total_price; } my @syohin_list = ('アンパン','food',100,'アンパン','food',100,'ペン','item',200); my $item_ref = &item(@syohin_list); my %syohin_list2 = %{$item_ref}; my $price = &total_price(\%syohin_list2); print "Total:$price yen "; 1;


※カンマ(,)を全角で表示しています。

抽象データ型としてItemクラスを導入しました。
total_priceサブルーチンは、そのItemクラスのインスタンスのリストを受け取るように修正しました。


仲の良いデータをまとめる

状況によってはこれで完成でもいいんですが、せっかくなのでもう少し責務を整理してみましょう。
今のtotal_priceサブルーチンで気になるのは、商品の品種に"food"とそれ以外の種類があることを知っていることです。具体的には以下の3つが気になります。


1.商品の品種が文字列であることを、total_priceサブルーチンは知っている。
2.商品の品種ごとの税率を知っているのがtotal_priceサブルーチンである。
3.商品の品種として、食べ物とそれ以外の2種類しかないということをtotal_priceサブルーチンが知っている。


(1)に関してですが、商品の品種が文字列であるかどうかはItemクラスの事情であって本質的にこのtotal_priceサブルーチンが知るべきことではないはずです。
  より重要なのは、(2)と(3)です。
(2)のポイントは、商品の品種ごとに税率が変わるということは、そもそも商品の品種と税率が仲の良いデータであるということです。
  商品の品種を管理するのはItemクラスで、税率を決めているのがtotal_priceサブルーチンです。ということは、Itemクラスとtotal_priceサブルーチンがとても仲が良いということになります。

これはあまり良くありません。2つのコンポーネント(クラスやサブルーチン)の仲が良すぎる関係は、どちらかに変更が入ると、もう一方にも修正が必要になるかもしれないということです。
これに対応するには、仲の良いデータ同士を1つにまとめることです。
つまり、税率をItemクラス内に持たせるべきで、Itemインスタンスが商品の品種ごとに適切な税率を返してくれれば良さそうです。


(3)も似たような問題です。total_priceサブルーチンがItemクラスの商品の品種のすべてを知っているのは、仲が良いということです。
これも解消しなくてはいけません。
そもそも、total_priceサブルーチンが商品の品種を管理しているのは税率のためでした。
ということは、税率データをItemクラスに移せば、(3)も解消できそうです。


# total_price2.pm package total_price2; use strict; sub new { my $class = shift; my $self = { my $price => my $total_price }; return bless $self,$class; } sub item { my @item_list = @_; my %items; my $i = 0; my $monoflg = 0; foreach(@item_list){ if($_ =~ /food/){ # food $monoflg = 1; }elsif($monoflg == 1){ $items{"food_$i"} = $_; # 100yen $items{"tax_food_1"} = 0.08; # tax_rate:税率8% $monoflg = 0; $i++; }elsif($_ =~ /item/){ #item $monoflg = 2; }elsif($monoflg == 2){ $items{"item_$i"} = $_; # 200yen $items{"tax_item_1"} = 0.1; # tax_rate:税率10% $monoflg = 0; $i++; } } return \%items; } sub total_price { my $hashref = shift; my %shina = %{$hashref}; my $t = 0; my $price = 0; my $total_price = 0; foreach(sort keys %shina){ if($_ =~ /food_$t/ ){ $price = $shina{"food_$t"} * ( 1 + $shina{"tax_food_1"}); # foodは8%消費税 }elsif($_ =~ /item_$t/ ){ $price = $shina{"item_$t"} * ( 1 + $shina{"tax_item_1"}); # itemは10%消費税 } $total_price = $total_price + $price; $price = 0; if($shina{"food_$t"} ne ''){ $t++; } } return $total_price; } my @syohin_list = ('アンパン','food',100,'アンパン','food',100,'ペン','item',200); my $item_ref = &item(@syohin_list); my %syohin_list2 = %{$item_ref}; my $price = &total_price(\%syohin_list2); print "Total:$price yen "; 1;


※カンマ(,)を全角で表示しています。

クラス定義が少し長くなりましたが、Itemクラス内でtax_rateを管理するようにしました。
Itemクラスのコンストラクタ内で、商品の品種に応じてtax_rateを決定するようにしましたが、これはItemクラスが商品の品種の全種類を管理することになります。
Itemクラスの責務としては自然ですね。

まだ続きます。


出典「日経クロステック 変更が難しいコードはNG、プログラミングでは「責務の分離」を意識すべし
いいね:7